#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<assert.h>
#include<unistd.h>

#define NUM 1024
#define OPT_NUM 64 //选项数

char lineCommand[NUM]; //命令行字符数组
char* myargv[OPT_NUM]; //指针数组
int lastCode = 0;
int lastSig = 0;

int main()
{
    while(1)
    {
        //输出提示符
        printf("用户名@主机名 当前路径#");
        fflush(stdout);//刷新缓冲区

        //获取用户输入，输入的时候,还会输入一个\n
        char* s = fgets(lineCommand,sizeof(lineCommand)-1,stdin);//按行获取字符串
        assert(s != NULL);
        (void)s;
        //输入时字符数组最后会有一个\n,要处理清除最后的\n
        lineCommand[strlen(lineCommand)-1] = 0;
        //printf("test:%s\n",lineCommand);
        
        //"ls -a -l -i" --> "ls" "-a" "-l" "-i"
        //切割字符串,放入命令行参数数组中
        myargv[0] = strtok(lineCommand," ");
        int i = 1;
        
        //处理ls指令默认带颜色
        if(myargv[0] != NULL && strcmp(myargv[0] , "ls") == 0)
        {
            myargv[i++] = (char*)"--color=auto";
        }
        //如果没有字串可以切割，strtok返回NULL,myargv[end] = NULL(命令行参数数组的最后一个也是NULL)
        while(myargv[i++] = strtok(NULL," "));
        
        //处理cd指令,如果是cd指令那么不需要创建子进程，让shell自己执行对应的命令，本质就是执行系统接口
        //像这种不需要创建子进程来执行，而是让shell自己执行的命令--叫做内建/内置命令
        //echo能查shell本地环境变量的原因是echo是一个内建/内置命令
        if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
        {
            if(myargv[1] != NULL) chdir(myargv[1]);
            continue;
        }

        //内建命令-->echo
        //echo指令
        if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0)
        {
            if(strcmp(myargv[1],"$?") == 0)
            {
                printf("%d,%d\n",lastCode,lastSig);
            }
            else
            {
                printf("%s\n",myargv[1]);
            }
            continue;
        }

        //测试是否成功
#ifdef DEBUG 
        for(int i = 0; myargv[i] ; i++)
        {
            printf("myargv[%d]:%s\n",i,myargv[i]);
        }
#endif
        
        //执行命令
        pid_t id = fork();
        assert(id != -1);//防止子进程没有建立
        
        if(id == 0)//子进程去调用输入的命令
        {
            execvp(myargv[0],myargv);//v:传递如何执行调用程序的参数放到数组传递，l:参数一个一个传递
                                     //p:传递调用程序的名称,会自动去PATH中查找
            exit(1);
        }
        
        //父进程用于等待子进程
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        assert(ret > 0);
        (void)ret;
        lastCode = ((status>>8)&0xFF);
        lastSig = (status&0x7f);
    }


    return 0;
}
